import { PiniaPluginContext, StoreState } from 'pinia'

export interface PersistedstateOptions {
	paths?: string[]
}

declare module 'pinia' {
	export interface DefineStoreOptionsBase<S, Store> {
		persistedstate?: PersistedstateOptions
	}
}

interface Storage {
	getItem: (key: string) => any
	setItem: (key: string, value: any) => void
	removeItem: (key: string) => void
}

interface Options<State> {
	paths?: string[]
	storage?: Storage
	overwrite?: boolean
	reducer?: (state: State, paths: string[]) => object
}

/**
 * @description: vuex 持久化插件
 * @param  { Options  } options 配置
 * @return { Function } (store: Store<S>) => void
 */
export default function <State>(pluginOptions?: Options<State>) {
	pluginOptions = pluginOptions || {}

	const storage = pluginOptions.storage || (window && window.sessionStorage)
	const storageCache: any = {}
	const pluginOptionPaths = pluginOptions.paths || []

	const _toString = Object.prototype.toString

	function getState(key: string, storage: Storage) {
		const value = storage.getItem(key)

		try {
			return typeof value === 'string' ? JSON.parse(value) : typeof value === 'object' ? value : undefined
		} catch (err) {}

		return undefined
	}

	function setState(key: string, state: any, storage: Storage) {
		return storage.setItem(key, JSON.stringify(state))
	}

	function subscriber(store: any) {
		return function (handler: Function, detached: object) {
			return store.$subscribe(handler, detached)
		}
	}

	const assertStorage = (storage: Storage) => {
		storage.setItem('@@', 1)
		storage.removeItem('@@')
	}

	assertStorage(storage)

	const fetchSavedState = (key: string) => {
		return getState(key, storage)
	}

	const pickObject = (obj: any, paths: string[]) => {
		if (!obj || typeof obj !== 'object') {
			return obj
		}
		// if (paths.length === 1) return { [paths[0]]: obj[paths[0]] }
		return paths.reduce((pre: any, key, i, arr) => {
			if (obj[key]) {
				if (paths.length === 1) {
					pre[key] = obj[key]
				} else if (_toString.call(obj[key]) === '[object Object]') {
					arr.shift()
					pre[key] = pickObject(obj[key], arr)
				}
			}

			return pre
		}, {})
	}

	function shallowMerge(obj1: any, obj2: any) {
		var isPlain1 = isPlainObject(obj1)
		var isPlain2 = isPlainObject(obj2)
		//只要obj1不是对象，那么不管obj2是不是对象，都用obj2直接替换obj1
		if (!isPlain1) return obj2
		//走到这一步时，说明obj1肯定是对象，那如果obj2不是对象，则还是以obj1为主
		if (!isPlain2) return obj1
		//如果上面两个条件都不成立，那说明obj1和obj2肯定都是对象， 则遍历obj2 进行合并
		let keys = [...Object.keys(obj2), ...Object.getOwnPropertySymbols(obj2)]
		keys.forEach(function (key) {
			obj1[key] = obj2[key]
		})

		return obj1
	}

	function isPlainObject(obj: any) {
		var proto, Ctor
		if (!obj || Object.prototype.toString.call(obj) !== '[object Object]') {
			return false
		}
		proto = Object.getPrototypeOf(obj)
		if (!proto) return true
		Ctor = Object.prototype.hasOwnProperty.call(proto, 'constructor') && proto.constructor
		return typeof Ctor === 'function' && Function.prototype.toString.call(Ctor) === Function.prototype.toString.call(Object)
	}

	function deepMerge(obj1: any, obj2: any) {
		var isPlain1 = isPlainObject(obj1)
		var isPlain2 = isPlainObject(obj2)
		//obj1或obj2中只要其中一个不是对象，则按照浅合并的规则进行合并
		if (!isPlain1 || !isPlain2) return shallowMerge(obj1, obj2)
		//如果都是对象，则进行每一层级的递归合并
		let keys = [...Object.keys(obj2), ...Object.getOwnPropertySymbols(obj2)]
		keys.forEach(function (key) {
			obj1[key] = deepMerge(obj1[key], obj2[key]) //这里递归调用
		})

		return obj1
	}

	const initPiniaPlugin = ({ pinia, app, options, store }: PiniaPluginContext) => {
		// 当 useStore 后调用
		const storeKey = store.$id
		const savedState = fetchSavedState(storeKey)
		const paths = options.persistedstate ? options.persistedstate.paths : null
		let isReset = true
		if (paths && paths.length < 1) {
			isReset = false
			delete storageCache[storeKey]
			storage.removeItem(storeKey)
		}

		if (typeof savedState === 'object' && savedState !== null) {
			isReset && store.$patch(pluginOptions?.overwrite ? savedState : deepMerge(store.$state, savedState))
		}

		if (pluginOptionPaths.length < 1 || (pluginOptionPaths.length > 0 && pluginOptionPaths.includes(storeKey))) {
			// 监听变化
			subscriber(store)(
				function (mutation: any, state: State) {
					if (paths && paths.length < 1) return

					if (!paths) {
						// 默认持久化
						storageCache[storeKey] = state
						setState(storeKey, storageCache[storeKey], storage)
					} else {
						// 根据 persistedstate.paths 持久化
						paths.forEach(k => {
							let keysArr = k.split('.')
							if (keysArr.length > 0) {
								storageCache[storeKey] = deepMerge(storageCache[storeKey], pickObject(state, keysArr))
								setState(storeKey, storageCache[storeKey], storage)
							}
						})
					}
				},
				{ detached: true }
			)
		}
	}

	return initPiniaPlugin
}
